/*
    Copyright 2006-2012 Patrik Jonsson, sunrise@familjenjonsson.org

    This file is part of Sunrise.

    Sunrise is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 3 of the License, or
    (at your option) any later version.

    Sunrise is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Sunrise.  If not, see <http://www.gnu.org/licenses/>.

*/

/// \file
/// Declarations of MPI utility functions. If we are not using MPI,
/// these are no-ops.

#ifndef __mpi_util__
#define __mpi_util__

#include "config.h"
#include <string>
#include <vector>
#include <cassert>
#include <blitz/array.h>

#ifdef HAVE_MPI

#include <boost/shared_ptr.hpp>

namespace mcrx {
  bool is_mpi_master();
  int mpi_rank();
  std::string mpi_rank_string();
  int mpi_size();
  void mpi_barrier();
  
  template<typename T, typename T_red>
  T mpi_reduce(const T& val, const T_red& functor);
  template<typename T> void mpi_broadcast(T& val);
  template<typename T> std::vector<T> mpi_collect(const T& val);

  template<typename T_array> void mpi_sum_arrays(T_array&);
  template<typename T_numtype, int N> 
  blitz::Array<T_numtype, N> mpi_stack_arrays(const blitz::Array<T_numtype, N>&, 
					      int);
  int mpi_calculate_offsets(int);

  inline int request_thread2tag(int thread) { assert(thread<1000); return 1000+thread; };
  inline int request_tag2thread(int tag) { return tag-1000; };
  inline int response_thread2tag(int thread) { return 2000+thread; };
  inline int response_tag2thread(int tag) { return tag-2000; };

  class mpienv;
  class mpienv_impl;
};

/** This singleton class initializes and finalizes the MPI
    environment. We can't use the Boost::mpi environment because it
    does not call MPI_Init_thread. */
class mcrx::mpienv {
private:
  mpienv();

  static boost::shared_ptr<mpienv_impl> pimpl;

public:
  ~mpienv() {};

  static void init(int argc, char** argv);
  static mpienv_impl* instance();
};

#else

namespace mcrx {
  inline bool is_mpi_master() { return true; };
  inline int mpi_rank() { return 0; };
  inline std::string mpi_rank_string() { return "0"; };
  inline int mpi_size() { return 1; };
  inline void mpi_barrier() {};

  template<typename T, typename T_red>
  T mpi_reduce(const T& val, const T_red& functor) { return val; };

  template<typename T> void mpi_broadcast(T& val) {};

  template<typename T> std::vector<T> mpi_collect(const T& val) {
    std::vector<T> v; v.push_back(val); return v; };

  template<typename T_array> void mpi_sum_arrays(T_array&) {};
  template<typename T_numtype, int N> 
  blitz::Array<T_numtype, N> mpi_stack_arrays(const blitz::Array<T_numtype, N>& a, 
					      int) { return a; };
  inline int mpi_calculate_offsets(int) { return 0; };

  inline int request_thread2tag(int thread) { assert(thread<1000); return 1000+thread; };
  inline int request_tag2thread(int tag) { return tag-1000; };
  inline int response_thread2tag(int thread) { return 2000+thread; };
  inline int response_tag2thread(int tag) { return tag-2000; };

  class mpienv {
  public:
    static void init(int argc, char** argv) {};
  };

}

#endif

#endif
